#!/usr/bin/python
# -*- coding: utf-8 -*-
# SERVERv5

#import BaseHTTPServer
#
#class http_server( BaseHTTPServer.BaseHTTPRequestHandler ):
#  do_POST( self ):
#    pass
#
#  do_HEAD( self ):
#    pass

import sys
import re
import socket
import thread

class http_io:
  """Implement HTTP proto data transfer for BlogoSerVer
  """
  def __init__(self, hostname='', hostport=5007, ):
    """hostname - ip address to bind
hostport - port to bind
    """
    # define const
    self.HEADER_MIN_LEN  = 512
    self.PACKET_SIZE     = 1024
    self.MAX_PACKET_SIZE = 1024*1024 # 1M
    self.MAX_QUEUE       = 15

    self.HOST = hostname
    self.PORT = hostport
    self.fileout = ''

    # status variables init
    self._cliCount_  = 0
    self._cliWait_   = 0
    self._serStatus_ = 0

    # client waiting timeouts
    self.packetWaitTimeOut = 3
    self.headerWaitTimeOut = 15

    # call backs list
    # called on new client connected: void cb_connect( char *, int )
    self.cb_connect    = None
    # called on data received, get responce: char *cb_data( char * )
    self.cb_request    = None
    # called on connection closed: void cb_disconnect( char *, int )
    self.cb_disconnect = None

    # regular expression compile
    self.ContentLength = re.compile( 'Content-Length: ([0-9]+)' )
    self.ContentType   = re.compile( 'Content-Type: ([a-zA-Z]+/[a-zA-Z]+)(; charset=([a-zA-Z-0-9]+))?' )

    # accept next mimtypes
    self.ACCEPTABLE_MIMETYPES = [ 'text/xml' ]

    # HTTP PROTO PART
    self.CHARSET = 'utf-8'
    self.BLANK = 'HTTP/1.1 200 OK\nServer: pentabion-lab\nContent-Type: text/xml; charset=%s\nConnection: close\nContent-Length: %d\n\n'
    self.HELLO = '<?xml version="1.0" encoding="utf-8"?>\n'

  def Status(self):
    sys.stderr.write('==============\n')
    sys.stderr.write('Server section\n')
    sys.stderr.write('--------------\n')
    sys.stderr.write('        Status:\t')
    if self._serStatus_:
      sys.stderr.write('Running\n')
    else:
      sys.stderr.write('Stoped\n')
    sys.stderr.write('==============\n')
    sys.stderr.write('Client section\n')
    sys.stderr.write('--------------\n')
    sys.stderr.write('       Waiting:\t%d\n' % (self._cliWait_) )
    sys.stderr.write('     Connected:\t%d\n' % (self._cliCount_) )

  def StartServer(self, writeboard=sys.stdout, timeout=None):
    if not self._serStatus_:
      self.sock = socket.socket( socket.AF_INET, socket.SOCK_STREAM )
      self.sock.setsockopt( socket.SOL_SOCKET, socket.SO_REUSEADDR, 1 )
      try:
        self.sock.bind( (self.HOST, self.PORT) )
      except:
        sys.stderr.write( '{E} Error while BINDing port\n' )
        sys.exit( 1 )
      self.sock.listen( self.MAX_QUEUE )
      self.sock.settimeout( timeout )
      self._serStatus_ = True

  def StopServer(self):
    if self._serStatus_:
      self.sock.shutdown( socket.SHUT_RDWR )
      self.sock.close()
      self._serStatus_ = False

  def _exit_thread_( self, msg=None, cliContext=None ):
    if not msg is None:
      sys.stderr.write( '[*] %s\n' % ( msg ) )

    self._cliWait_  += 1
    self._cliCount_ -= 1

    if not cliContext is None:
      try:
        #cliContext[1].shutdown( socket.SHUT_RDWR )
        cliContext[1].close()
      except:
        sys.stderr.write( '{W} fault to shutdown client socket' )

      if not self.cb_disconnect is None:
        try:
          self.cb_disconnect( cliContext )
        except:
          sys.stderr.write( '{W} Skip DISCONNECT hook causes error occured\n' )

    else:
      sys.stderr.write( '{W} cliContext is None' )

    thread.exit_thread()

  def _getHTTPHeaders_( self, cliContext ):

    cliContext[1].settimeout( self.headerWaitTimeOut )

    # read headers...
    data = ''
    try:
      data = cliContext[1].recv( self.PACKET_SIZE )
    except:
      self._exit_thread_( 'Data transfer timeout exceeded', cliContext )

    while len( data ) < self.HEADER_MIN_LEN:
      try:
        data += cliContext[1].recv( self.PACKET_SIZE )
      except:
        self._exit_thread_( 'Data transfer timeout exceeded', cliContext )

    # chk for POST method in HTTP headers
    if data[:4].lower() != 'post':
      self._exit_thread_( 'POST chk failed', cliContext )

    # find Content defined vars
    res = self.ContentLength.search( data )
    if res is None:
      self._exit_thread_( 'Cannot determine Content-Length', cliContext )

    ContentLength = int( res.groups( )[0] )
    if ContentLength > self.MAX_PACKET_SIZE: # exit causes too large packet
      self._exit_thread_( 'Content-Length more then MAX_PACKET_SIZE', cliContext )

    res = self.ContentType.search( data )
    if res is None or not res.groups( )[0].lower() in self.ACCEPTABLE_MIMETYPES:
      self._exit_thread_( 'Wrong Content-Type or not set', cliContext )

    del res

    # find doc start
    pos = data.find( '\n\n')
    if pos == -1:
      pos = data.find( '\r\n\r\n' )

    if pos == -1:
      self._exit_thread_( 'Wrong document format', cliContext )

    # read document tail
    return ( ContentLength, data[pos+2:] )


  def _getRequest_( self, cliContext ):

    ContentLength, data = self._getHTTPHeaders_( cliContext )

    cliContext[1].settimeout( self.packetWaitTimeOut )

    while len( data ) < ContentLength:
      try:
        data += cliContext[1].recv( min(self.PACKET_SIZE, ContentLength-len( data ) ) )
      except socket.timeout:
        self._exit_thread_( 'Data transfer timeout exceeded', cliContext )

    return data


  def _sendResponce_( self, data, cliContext ):
    try:
      cliContext[1].send( self.BLANK % ( self.CHARSET, len( data ) ) )
      cliContext[1].send( data )
    except:
      self._exit_thread_( 'Broken pipe while sending response', cliContext )


  def Client( self, cliConnection, cliAddress ):
    """Wait for client connection, send greating to client"""

    # Decrease waiting client counter
    self._cliWait_  -= 1
    # Increase client count, client connected!
    self._cliCount_ += 1

    cliContext = ( thread.get_ident(), cliConnection, cliAddress )
    #cliContext[1].settimeout( 10 )

    if not self.cb_connect is None:
      try:
        self.cb_connect( cliContext )

      except:
        sys.stderr.write( '{W} Skip CONNECT hook causes error occured\n' )

    ############### START CYCLE SECTION ########################

    while self._serStatus_:
      # get request here
      data = self._getRequest_( cliContext )
  
      # all data readed. Call HOOK, send response
      if not self.cb_request is None:
#        try:
        data = self.cb_request( data, cliContext )
#        except:
#          sys.stderr.write( '{W} Skip REQUEST hook causes error occured\n' )
  
      else: # callback hook is not set. Return nothing
        data = ''
  
      try:
        sys.stderr.write( '[*] Send responce to client: %s\n' % ( data ) )
      except:
        sys.stderr.write( '[*] Send responce to client: <data could not be shown>\n' )

      # send response
      self._sendResponce_( data, cliContext )

    ############### STOP CYCLE SECTION ########################

    # exit thread normally
    self._exit_thread_( 'Exit thread', cliContext )

  # Wait for <count> of client (All client has it's unique thread)
  def WaitFor(self, count=1):

    if self._serStatus_:
      self._cliWait_ = count
      # Start new thread for new client...
      while( self._cliWait_ > 0 ):

        try:
          cliConnection, cliAddress = self.sock.accept()
          #cliConnection.setsockopt( socket.SOL_SOCKET, socket.SO_REUSEADDR, 1 )

        except:
          sys.stderr.write( 'Error while waiting for client\n' )
          break

        newclient = thread.start_new_thread( self.Client, ( cliConnection, cliAddress ) )

